Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Displaying Raw • Download


meshchatx/src/backend/page_node.py b0d47b9ece2aac27f9b52d71eead961754287bf5 (b0d47b9e) Text, 16.22 KB

T8b949e# SPDX-License-Identifier: 0BSD

T8b949e"""PageNode: Serves Micron pages and files over RNS.

Each PageNode owns an RNS Destination (SINGLE, IN) with the aspect
nomadnetwork.node and registers per-page request handlers at
paths like /page/index.mu. This makes page nodes compatible with
the standard NomadNet page browsing protocol (which is just RNS
request/response with specific path conventions).

Clients link to the destination and call link.request("/page/name.mu")
to fetch a page, or /file/name for files.

Supported page filename extensions are .mu, .md, .txt, and .html.
"""

Tff7b72import T7ee787json
Tff7b72import T7ee787os
Tff7b72import T7ee787time

Tff7b72import T7ee787RNS

Te6edf3APP_NAME Tff7b72= Ta5d6ff"Ta5d6ffnomadnetworkTa5d6ff"
Te6edf3ASPECT Tff7b72= Ta5d6ff"Ta5d6ffnodeTa5d6ff"
Te6edf3DEFAULT_INDEX Tff7b72= Ta5d6ff"Ta5d6ffindex.muTa5d6ff"

Te6edf3ALLOWED_PAGE_EXTENSIONS Tff7b72= Tffa657frozensetTb4b4b4(Tb4b4b4{Ta5d6ff"Ta5d6ff.muTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff.mdTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff.txtTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff.htmlTa5d6ff"Tb4b4b4}Tb4b4b4)


Tff7b72def Td2a8ffnormalize_page_filenameTb4b4b4(Te6edf3nameTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tffa657strTb4b4b4:
T8b949e"""Return a safe basename with an allowed extension. Unknown extensions raise ValueError."""
Te6edf3name Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffbasenameTb4b4b4(Tb4b4b4(Te6edf3name Tff7b72or Ta5d6ff"Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3name Tff7b72or Te6edf3name Tff7b72in Tb4b4b4(Ta5d6ff"Ta5d6ff.Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff..Ta5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72raise Tf85149ValueErrorTb4b4b4(Ta5d6ff"Ta5d6ffpage name is requiredTa5d6ff"Tb4b4b4)
Tff7b72if Ta5d6ff"Ta5d6ff/Ta5d6ff" Tff7b72in Te6edf3name Tff7b72or Ta5d6ff"Tffea00\\Ta5d6ff" Tff7b72in Te6edf3nameTb4b4b4:
Tff7b72raise Tf85149ValueErrorTb4b4b4(Ta5d6ff"Ta5d6ffinvalid page nameTa5d6ff"Tb4b4b4)
Te6edf3lower Tff7b72= Te6edf3nameTff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)
Tff7b72for Te6edf3ext Tff7b72in Te6edf3ALLOWED_PAGE_EXTENSIONSTb4b4b4:
Tff7b72if Te6edf3lowerTff7b72.Td2a8ffendswithTb4b4b4(Te6edf3extTb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3name
Tff7b72if Ta5d6ff"Ta5d6ff.Ta5d6ff" Tff7b72in Te6edf3nameTb4b4b4:
Tff7b72raise Tf85149ValueErrorTb4b4b4(Ta5d6ff"Ta5d6ffunsupported page extensionTa5d6ff"Tb4b4b4)
Tff7b72return Ta5d6fffTa5d6ff"Tffd700{Te6edf3nameTffd700}Ta5d6ff.muTa5d6ff"


Tff7b72def Td2a8ffis_allowed_page_filenameTb4b4b4(Te6edf3nameTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tffa657boolTb4b4b4:
Te6edf3lower Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffbasenameTb4b4b4(Te6edf3name Tff7b72or Ta5d6ff"Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)
Tff7b72return Tffa657anyTb4b4b4(Te6edf3lowerTff7b72.Td2a8ffendswithTb4b4b4(Te6edf3extTb4b4b4) Tff7b72for Te6edf3ext Tff7b72in Te6edf3ALLOWED_PAGE_EXTENSIONSTb4b4b4)


Tff7b72def Td2a8ff_safe_mesh_file_basenameTb4b4b4(Te6edf3nameTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tffa657strTb4b4b4:
T8b949e"""Reject empty, dot, or parent-segment names after basename (path traversal)."""
Te6edf3base Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffbasenameTb4b4b4(Tb4b4b4(Te6edf3name Tff7b72or Ta5d6ff"Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3base Tff7b72or Te6edf3base Tff7b72in Tb4b4b4(Ta5d6ff"Ta5d6ff.Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff..Ta5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72raise Tf85149ValueErrorTb4b4b4(Ta5d6ff"Ta5d6ffinvalid file nameTa5d6ff"Tb4b4b4)
Tff7b72if Ta5d6ff"Ta5d6ff/Ta5d6ff" Tff7b72in Te6edf3base Tff7b72or Ta5d6ff"Tffea00\\Ta5d6ff" Tff7b72in Te6edf3baseTb4b4b4:
Tff7b72raise Tf85149ValueErrorTb4b4b4(Ta5d6ff"Ta5d6ffinvalid file nameTa5d6ff"Tb4b4b4)
Tff7b72return Te6edf3base


Tff7b72def Td2a8ff_reject_name_component_too_longTb4b4b4(Te6edf3parent_dirTb4b4b4: Tffa657strTb4b4b4, Te6edf3componentTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tff7b72NoneTb4b4b4:
T8b949e"""Raise ValueError if basename exceeds this directory's filename length limit."""
Tff7b72tryTb4b4b4:
Tff7b72if Te6edf3parent_dir Tff7b72and Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisdirTb4b4b4(Te6edf3parent_dirTb4b4b4)Tb4b4b4:
Te6edf3max_bytes Tff7b72= Tffa657intTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathconfTb4b4b4(Te6edf3parent_dirTb4b4b4, Ta5d6ff"Ta5d6ffPC_NAME_MAXTa5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3max_bytes Tff7b72= T79c0ff255
Tff7b72except Tb4b4b4(Tf85149OSErrorTb4b4b4, Tf85149ValueErrorTb4b4b4, Tf85149TypeErrorTb4b4b4, Tf85149OverflowErrorTb4b4b4)Tb4b4b4:
Te6edf3max_bytes Tff7b72= T79c0ff255
Tff7b72if Tffa657lenTb4b4b4(Te6edf3osTff7b72.Td2a8fffsencodeTb4b4b4(Te6edf3componentTb4b4b4)Tb4b4b4) Tff7b72> Te6edf3max_bytesTb4b4b4:
Tff7b72raise Tf85149ValueErrorTb4b4b4(Ta5d6ff"Ta5d6ffname too longTa5d6ff"Tb4b4b4)


Tff7b72class T56d364PageNodeTb4b4b4:
T8b949e"""A single page-serving node on the Reticulum mesh."""

Tff7b72def Tff7b72__init__Tb4b4b4(Tff7b72selfTb4b4b4, Te6edf3node_idTb4b4b4, Te6edf3nameTb4b4b4, Te6edf3base_dirTb4b4b4, Te6edf3identityTff7b72=Tff7b72NoneTb4b4b4, Te6edf3identity_pathTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffnode_id Tff7b72= Te6edf3node_id
Tff7b72selfTff7b72.Td2a8ffname Tff7b72= Te6edf3name
Tff7b72selfTff7b72.Td2a8ffbase_dir Tff7b72= Te6edf3base_dir
Tff7b72selfTff7b72.Td2a8ffpages_dir Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3base_dirTb4b4b4, Ta5d6ff"Ta5d6ffpagesTa5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8fffiles_dir Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3base_dirTb4b4b4, Ta5d6ff"Ta5d6fffilesTa5d6ff"Tb4b4b4)

Tff7b72selfTff7b72.Td2a8ffidentity Tff7b72= Te6edf3identity
Tff7b72selfTff7b72.Td2a8ffidentity_path Tff7b72= Te6edf3identity_path Tff7b72or Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3base_dirTb4b4b4, Ta5d6ff"Ta5d6ffidentityTa5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffdestination Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ffactive_links Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72selfTff7b72.Td2a8ffrunning Tff7b72= Tff7b72False
Tff7b72selfTff7b72.Td2a8ff_registered_page_paths Tff7b72= Tffa657setTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_registered_file_paths Tff7b72= Tffa657setTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_stats Tff7b72= Tb4b4b4{Ta5d6ff"Ta5d6ffpages_servedTa5d6ff"Tb4b4b4: T79c0ff0Tb4b4b4, Ta5d6ff"Ta5d6fffiles_servedTa5d6ff"Tb4b4b4: T79c0ff0Tb4b4b4, Ta5d6ff"Ta5d6fflinks_establishedTa5d6ff"Tb4b4b4: T79c0ff0Tb4b4b4}
Tff7b72selfTff7b72.Td2a8ff_serve_started_at Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ff_unique_remote_hashes Tff7b72= Tffa657setTb4b4b4(Tb4b4b4)

Tff7b72def Td2a8ffsetupTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Create directories, load or create identity, set up RNS destination."""
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Tff7b72selfTff7b72.Td2a8ffpages_dirTb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Tff7b72selfTff7b72.Td2a8fffiles_dirTb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)

Tff7b72if Tff7b72selfTff7b72.Td2a8ffidentity Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Tff7b72selfTff7b72.Td2a8ffidentity_pathTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffidentity Tff7b72= Te6edf3RNSTff7b72.Td2a8ffIdentityTff7b72.Td2a8fffrom_fileTb4b4b4(Tff7b72selfTff7b72.Td2a8ffidentity_pathTb4b4b4)
Tff7b72elseTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffidentity Tff7b72= Te6edf3RNSTff7b72.Td2a8ffIdentityTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffidentityTff7b72.Td2a8ffto_fileTb4b4b4(Tff7b72selfTff7b72.Td2a8ffidentity_pathTb4b4b4)

Tff7b72selfTff7b72.Td2a8ffdestination Tff7b72= Te6edf3RNSTff7b72.Td2a8ffDestinationTb4b4b4(
Tff7b72selfTff7b72.Td2a8ffidentityTb4b4b4,
Te6edf3RNSTff7b72.Td2a8ffDestinationTff7b72.Td2a8ffINTb4b4b4,
Te6edf3RNSTff7b72.Td2a8ffDestinationTff7b72.Td2a8ffSINGLETb4b4b4,
Te6edf3APP_NAMETb4b4b4,
Te6edf3ASPECTTb4b4b4,
Tb4b4b4)

Tff7b72selfTff7b72.Td2a8ffdestinationTff7b72.Td2a8ffset_link_established_callbackTb4b4b4(Tff7b72selfTff7b72.Td2a8ff_link_establishedTb4b4b4)

Tff7b72selfTff7b72.Td2a8ff_register_existing_pagesTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_register_existing_filesTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_ensure_local_pathTb4b4b4(Tb4b4b4)

Tff7b72selfTff7b72.Td2a8ffrunning Tff7b72= Tff7b72True
Tff7b72selfTff7b72.Td2a8ff_serve_started_at Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
Tff7b72return Tff7b72selfTff7b72.Td2a8ffdestinationTff7b72.Td2a8ffhashTff7b72.Td2a8ffhexTb4b4b4(Tb4b4b4)

Tff7b72def Td2a8ffannounceTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Broadcast this node's presence on the mesh."""
Tff7b72if Tff7b72selfTff7b72.Td2a8ffdestination Tff7b72and Tff7b72selfTff7b72.Td2a8ffrunningTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_register_existing_filesTb4b4b4(Tb4b4b4)
Te6edf3app_data Tff7b72= Tff7b72selfTff7b72.Td2a8ffnameTff7b72.Td2a8ffencodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffdestinationTff7b72.Td2a8ffannounceTb4b4b4(Te6edf3app_dataTff7b72=Te6edf3app_dataTb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_ensure_local_pathTb4b4b4(Tb4b4b4)

Tff7b72def Td2a8ffteardownTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Deregister handlers and clean up."""
Tff7b72selfTff7b72.Td2a8ffrunning Tff7b72= Tff7b72False
Tff7b72selfTff7b72.Td2a8ff_serve_started_at Tff7b72= Tff7b72None
Tff7b72selfTff7b72.Td2a8ff_unique_remote_hashesTff7b72.Td2a8ffclearTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72selfTff7b72.Td2a8ffdestinationTb4b4b4:
Tff7b72for Te6edf3rpath Tff7b72in Tffa657listTb4b4b4(Tff7b72selfTff7b72.Td2a8ff_registered_page_pathsTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffdestinationTff7b72.Td2a8ffderegister_request_handlerTb4b4b4(Te6edf3rpathTb4b4b4)
Tff7b72for Te6edf3rpath Tff7b72in Tffa657listTb4b4b4(Tff7b72selfTff7b72.Td2a8ff_registered_file_pathsTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffdestinationTff7b72.Td2a8ffderegister_request_handlerTb4b4b4(Te6edf3rpathTb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_registered_page_pathsTff7b72.Td2a8ffclearTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_registered_file_pathsTff7b72.Td2a8ffclearTb4b4b4(Tb4b4b4)

Te6edf3RNSTff7b72.Td2a8ffTransportTff7b72.Td2a8ffderegister_destinationTb4b4b4(Tff7b72selfTff7b72.Td2a8ffdestinationTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffdestination Tff7b72= Tff7b72None

Tff7b72for Te6edf3link Tff7b72in Tffa657listTb4b4b4(Tff7b72selfTff7b72.Td2a8ffactive_linksTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3linkTff7b72.Td2a8ffteardownTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Tff7b72selfTff7b72.Td2a8ffactive_linksTff7b72.Td2a8ffclearTb4b4b4(Tb4b4b4)

Tff7b72def Td2a8ff_note_remote_identityTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3remote_identityTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3remote_identity Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return
Tff7b72tryTb4b4b4:
Te6edf3h Tff7b72= Tffa657getattrTb4b4b4(Te6edf3remote_identityTb4b4b4, Ta5d6ff"Ta5d6ffhashTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)
Tff7b72if Te6edf3h Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_unique_remote_hashesTff7b72.Td2a8ffaddTb4b4b4(Te6edf3hTff7b72.Td2a8ffhexTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass

Tff7b72def Td2a8ff_link_establishedTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3linkTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffactive_linksTff7b72.Td2a8ffappendTb4b4b4(Te6edf3linkTb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_statsTb4b4b4[Ta5d6ff"Ta5d6fflinks_establishedTa5d6ff"Tb4b4b4] Tff7b72+Tff7b72= T79c0ff1
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_note_remote_identityTb4b4b4(Te6edf3linkTff7b72.Td2a8ffget_remote_identityTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Te6edf3linkTff7b72.Td2a8ffset_link_closed_callbackTb4b4b4(Tff7b72selfTff7b72.Td2a8ff_link_closedTb4b4b4)

Tff7b72def Td2a8ff_link_closedTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3linkTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3link Tff7b72in Tff7b72selfTff7b72.Td2a8ffactive_linksTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffactive_linksTff7b72.Td2a8ffremoveTb4b4b4(Te6edf3linkTb4b4b4)

Tff7b72def Td2a8ff_ensure_local_pathTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Register this identity in RNS.Identity.known_destinations.

Lets Identity.recall() resolve the destination for local link setup.
"""
Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8ffdestinationTb4b4b4:
Tff7b72return
Te6edf3RNSTff7b72.Td2a8ffIdentityTff7b72.Td2a8ffrememberTb4b4b4(
Te6edf3packet_hashTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3destination_hashTff7b72=Tff7b72selfTff7b72.Td2a8ffdestinationTff7b72.Td2a8ffhashTb4b4b4,
Te6edf3public_keyTff7b72=Tff7b72selfTff7b72.Td2a8ffidentityTff7b72.Td2a8ffget_public_keyTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3app_dataTff7b72=Tff7b72selfTff7b72.Td2a8ffnameTff7b72.Td2a8ffencodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ff_page_request_pathTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3page_nameTb4b4b4)Tb4b4b4:
T8b949e"""Build the NomadNet-style request path for a page."""
Tff7b72return Ta5d6fffTa5d6ff"Ta5d6ff/page/Tffd700{Te6edf3page_nameTffd700}Ta5d6ff"

Tff7b72def Td2a8ff_file_request_pathTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3file_nameTb4b4b4)Tb4b4b4:
T8b949e"""Build the NomadNet-style request path for a file."""
Tff7b72return Ta5d6fffTa5d6ff"Ta5d6ff/file/Tffd700{Te6edf3file_nameTffd700}Ta5d6ff"

Tff7b72def Td2a8ff_register_existing_pagesTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Scan pages directory and register a handler for each page."""
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisdirTb4b4b4(Tff7b72selfTff7b72.Td2a8ffpages_dirTb4b4b4)Tb4b4b4:
Tff7b72return
Tff7b72for Te6edf3fname Tff7b72in Te6edf3osTff7b72.Td2a8fflistdirTb4b4b4(Tff7b72selfTff7b72.Td2a8ffpages_dirTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Tff7b72selfTff7b72.Td2a8ffpages_dirTb4b4b4, Te6edf3fnameTb4b4b4)Tb4b4b4)Tb4b4b4:
Tff7b72continue
Tff7b72if Tff7b72not Te6edf3is_allowed_page_filenameTb4b4b4(Te6edf3fnameTb4b4b4)Tb4b4b4:
Tff7b72continue
Tff7b72selfTff7b72.Td2a8ff_register_page_handlerTb4b4b4(Te6edf3fnameTb4b4b4)

Tff7b72def Td2a8ff_register_existing_filesTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Scan files directory and register a handler for each file."""
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisdirTb4b4b4(Tff7b72selfTff7b72.Td2a8fffiles_dirTb4b4b4)Tb4b4b4:
Tff7b72return
Tff7b72for Te6edf3fname Tff7b72in Te6edf3osTff7b72.Td2a8fflistdirTb4b4b4(Tff7b72selfTff7b72.Td2a8fffiles_dirTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Tff7b72selfTff7b72.Td2a8fffiles_dirTb4b4b4, Te6edf3fnameTb4b4b4)Tb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_register_file_handlerTb4b4b4(Te6edf3fnameTb4b4b4)

Tff7b72def Td2a8ff_register_page_handlerTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3page_nameTb4b4b4)Tb4b4b4:
T8b949e"""Register a request handler for a specific page."""
Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8ffdestinationTb4b4b4:
Tff7b72return
Te6edf3rpath Tff7b72= Tff7b72selfTff7b72.Td2a8ff_page_request_pathTb4b4b4(Te6edf3page_nameTb4b4b4)
Tff7b72if Te6edf3rpath Tff7b72in Tff7b72selfTff7b72.Td2a8ff_registered_page_pathsTb4b4b4:
Tff7b72return
Tff7b72selfTff7b72.Td2a8ffdestinationTff7b72.Td2a8ffregister_request_handlerTb4b4b4(
Te6edf3rpathTb4b4b4,
Te6edf3response_generatorTff7b72=Tff7b72selfTff7b72.Td2a8ff_make_page_responderTb4b4b4(Te6edf3page_nameTb4b4b4)Tb4b4b4,
Te6edf3allowTff7b72=Te6edf3RNSTff7b72.Td2a8ffDestinationTff7b72.Td2a8ffALLOW_ALLTb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_registered_page_pathsTff7b72.Td2a8ffaddTb4b4b4(Te6edf3rpathTb4b4b4)

Tff7b72def Td2a8ff_deregister_page_handlerTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3page_nameTb4b4b4)Tb4b4b4:
T8b949e"""Deregister the request handler for a specific page."""
Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8ffdestinationTb4b4b4:
Tff7b72return
Te6edf3rpath Tff7b72= Tff7b72selfTff7b72.Td2a8ff_page_request_pathTb4b4b4(Te6edf3page_nameTb4b4b4)
Tff7b72if Te6edf3rpath Tff7b72not Tff7b72in Tff7b72selfTff7b72.Td2a8ff_registered_page_pathsTb4b4b4:
Tff7b72return
Tff7b72selfTff7b72.Td2a8ffdestinationTff7b72.Td2a8ffderegister_request_handlerTb4b4b4(Te6edf3rpathTb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_registered_page_pathsTff7b72.Td2a8ffdiscardTb4b4b4(Te6edf3rpathTb4b4b4)

Tff7b72def Td2a8ff_register_file_handlerTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3file_nameTb4b4b4)Tb4b4b4:
T8b949e"""Register a request handler for a specific file."""
Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8ffdestinationTb4b4b4:
Tff7b72return
Te6edf3rpath Tff7b72= Tff7b72selfTff7b72.Td2a8ff_file_request_pathTb4b4b4(Te6edf3file_nameTb4b4b4)
Tff7b72if Te6edf3rpath Tff7b72in Tff7b72selfTff7b72.Td2a8ff_registered_file_pathsTb4b4b4:
Tff7b72return
Tff7b72selfTff7b72.Td2a8ffdestinationTff7b72.Td2a8ffregister_request_handlerTb4b4b4(
Te6edf3rpathTb4b4b4,
Te6edf3response_generatorTff7b72=Tff7b72selfTff7b72.Td2a8ff_make_file_responderTb4b4b4(Te6edf3file_nameTb4b4b4)Tb4b4b4,
Te6edf3allowTff7b72=Te6edf3RNSTff7b72.Td2a8ffDestinationTff7b72.Td2a8ffALLOW_ALLTb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_registered_file_pathsTff7b72.Td2a8ffaddTb4b4b4(Te6edf3rpathTb4b4b4)

Tff7b72def Td2a8ff_deregister_file_handlerTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3file_nameTb4b4b4)Tb4b4b4:
T8b949e"""Deregister the request handler for a specific file."""
Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8ffdestinationTb4b4b4:
Tff7b72return
Te6edf3rpath Tff7b72= Tff7b72selfTff7b72.Td2a8ff_file_request_pathTb4b4b4(Te6edf3file_nameTb4b4b4)
Tff7b72if Te6edf3rpath Tff7b72not Tff7b72in Tff7b72selfTff7b72.Td2a8ff_registered_file_pathsTb4b4b4:
Tff7b72return
Tff7b72selfTff7b72.Td2a8ffdestinationTff7b72.Td2a8ffderegister_request_handlerTb4b4b4(Te6edf3rpathTb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_registered_file_pathsTff7b72.Td2a8ffdiscardTb4b4b4(Te6edf3rpathTb4b4b4)

Tff7b72def Td2a8ff_make_page_responderTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3page_nameTb4b4b4)Tb4b4b4:
T8b949e"""Return a closure that serves a specific page."""

Tff7b72def Td2a8ffresponderTb4b4b4(Te6edf3pathTb4b4b4, Te6edf3dataTb4b4b4, Te6edf3request_idTb4b4b4, Te6edf3link_idTb4b4b4, Te6edf3remote_identityTb4b4b4, Te6edf3requested_atTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_note_remote_identityTb4b4b4(Te6edf3remote_identityTb4b4b4)
Te6edf3safe_name Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffbasenameTb4b4b4(Te6edf3page_nameTb4b4b4)
Te6edf3page_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Tff7b72selfTff7b72.Td2a8ffpages_dirTb4b4b4, Te6edf3safe_nameTb4b4b4)
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3page_pathTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Tff7b72tryTb4b4b4:
Tff7b72with Tffa657openTb4b4b4(Te6edf3page_pathTb4b4b4, Ta5d6ff"Ta5d6ffrbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3content Tff7b72= Te6edf3fTff7b72.Td2a8ffreadTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_statsTb4b4b4[Ta5d6ff"Ta5d6ffpages_servedTa5d6ff"Tb4b4b4] Tff7b72+Tff7b72= T79c0ff1
Tff7b72return Te6edf3content
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72return Tff7b72None

Tff7b72return Te6edf3responder

Tff7b72def Td2a8ff_make_file_responderTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3file_nameTb4b4b4)Tb4b4b4:
T8b949e"""Return a closure that serves a specific file."""

Tff7b72def Td2a8ffresponderTb4b4b4(Te6edf3pathTb4b4b4, Te6edf3dataTb4b4b4, Te6edf3request_idTb4b4b4, Te6edf3link_idTb4b4b4, Te6edf3remote_identityTb4b4b4, Te6edf3requested_atTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_note_remote_identityTb4b4b4(Te6edf3remote_identityTb4b4b4)
Te6edf3safe_name Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffbasenameTb4b4b4(Te6edf3file_nameTb4b4b4)
Te6edf3file_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Tff7b72selfTff7b72.Td2a8fffiles_dirTb4b4b4, Te6edf3safe_nameTb4b4b4)
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3file_pathTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Tff7b72tryTb4b4b4:
Te6edf3fh Tff7b72= Tffa657openTb4b4b4(Te6edf3file_pathTb4b4b4, Ta5d6ff"Ta5d6ffrbTa5d6ff"Tb4b4b4)
Te6edf3metadata Tff7b72= Tb4b4b4{Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4: Te6edf3safe_nameTff7b72.Td2a8ffencodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)Tb4b4b4}
Tff7b72selfTff7b72.Td2a8ff_statsTb4b4b4[Ta5d6ff"Ta5d6fffiles_servedTa5d6ff"Tb4b4b4] Tff7b72+Tff7b72= T79c0ff1
Tff7b72return Tb4b4b4[Te6edf3fhTb4b4b4, Te6edf3metadataTb4b4b4]
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72return Tff7b72None

Tff7b72return Te6edf3responder

Tff7b72def Td2a8ffadd_pageTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3nameTb4b4b4, Te6edf3contentTb4b4b4)Tb4b4b4:
T8b949e"""Write a page file and register its request handler."""
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Tff7b72selfTff7b72.Td2a8ffpages_dirTb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
Te6edf3name Tff7b72= Te6edf3normalize_page_filenameTb4b4b4(Te6edf3nameTb4b4b4)
Te6edf3_reject_name_component_too_longTb4b4b4(Tff7b72selfTff7b72.Td2a8ffpages_dirTb4b4b4, Te6edf3nameTb4b4b4)
Te6edf3page_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Tff7b72selfTff7b72.Td2a8ffpages_dirTb4b4b4, Te6edf3nameTb4b4b4)
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3contentTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Te6edf3content Tff7b72= Te6edf3contentTff7b72.Td2a8ffencodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)
Tff7b72with Tffa657openTb4b4b4(Te6edf3page_pathTb4b4b4, Ta5d6ff"Ta5d6ffwbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3fTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3contentTb4b4b4)
Tff7b72if Tff7b72selfTff7b72.Td2a8ffrunningTb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_register_page_handlerTb4b4b4(Te6edf3nameTb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tff7b72raise Tf85149RuntimeErrorTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffPage written but failed to register on mesh: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4) Tff7b72from T7ee787e
Tff7b72return Te6edf3name

Tff7b72def Td2a8ffremove_pageTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3nameTb4b4b4)Tb4b4b4:
T8b949e"""Remove a page and deregister its request handler."""
Tff7b72tryTb4b4b4:
Te6edf3name Tff7b72= Te6edf3normalize_page_filenameTb4b4b4(Te6edf3nameTb4b4b4)
Te6edf3_reject_name_component_too_longTb4b4b4(Tff7b72selfTff7b72.Td2a8ffpages_dirTb4b4b4, Te6edf3nameTb4b4b4)
Tff7b72except Tf85149ValueErrorTb4b4b4:
Tff7b72return Tff7b72False
Te6edf3page_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Tff7b72selfTff7b72.Td2a8ffpages_dirTb4b4b4, Te6edf3nameTb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3page_pathTb4b4b4)Tb4b4b4:
Te6edf3osTff7b72.Td2a8ffremoveTb4b4b4(Te6edf3page_pathTb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_deregister_page_handlerTb4b4b4(Te6edf3nameTb4b4b4)
Tff7b72return Tff7b72True
Tff7b72return Tff7b72False

Tff7b72def Td2a8fflist_pagesTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Return a list of page names."""
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisdirTb4b4b4(Tff7b72selfTff7b72.Td2a8ffpages_dirTb4b4b4)Tb4b4b4:
Tff7b72return Tb4b4b4[Tb4b4b4]
Tff7b72return Tffa657sortedTb4b4b4(
Te6edf3f
Tff7b72for Te6edf3f Tff7b72in Te6edf3osTff7b72.Td2a8fflistdirTb4b4b4(Tff7b72selfTff7b72.Td2a8ffpages_dirTb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Tff7b72selfTff7b72.Td2a8ffpages_dirTb4b4b4, Te6edf3fTb4b4b4)Tb4b4b4)
Tff7b72and Te6edf3is_allowed_page_filenameTb4b4b4(Te6edf3fTb4b4b4)
Tb4b4b4)

Tff7b72def Td2a8ffget_page_contentTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3nameTb4b4b4)Tb4b4b4:
T8b949e"""Read and return a page's content."""
Tff7b72tryTb4b4b4:
Te6edf3name Tff7b72= Te6edf3normalize_page_filenameTb4b4b4(Te6edf3nameTb4b4b4)
Te6edf3_reject_name_component_too_longTb4b4b4(Tff7b72selfTff7b72.Td2a8ffpages_dirTb4b4b4, Te6edf3nameTb4b4b4)
Tff7b72except Tf85149ValueErrorTb4b4b4:
Tff7b72return Tff7b72None
Te6edf3page_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Tff7b72selfTff7b72.Td2a8ffpages_dirTb4b4b4, Te6edf3nameTb4b4b4)
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3page_pathTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Tff7b72with Tffa657openTb4b4b4(Te6edf3page_pathTb4b4b4, Te6edf3encodingTff7b72=Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Tff7b72return Te6edf3fTff7b72.Td2a8ffreadTb4b4b4(Tb4b4b4)

Tff7b72def Td2a8ffadd_fileTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3nameTb4b4b4, Te6edf3dataTb4b4b4)Tb4b4b4:
T8b949e"""Write a file and register its request handler."""
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Tff7b72selfTff7b72.Td2a8fffiles_dirTb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
Te6edf3name Tff7b72= Te6edf3_safe_mesh_file_basenameTb4b4b4(Te6edf3nameTb4b4b4)
Te6edf3_reject_name_component_too_longTb4b4b4(Tff7b72selfTff7b72.Td2a8fffiles_dirTb4b4b4, Te6edf3nameTb4b4b4)
Te6edf3file_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Tff7b72selfTff7b72.Td2a8fffiles_dirTb4b4b4, Te6edf3nameTb4b4b4)
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3dataTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Te6edf3data Tff7b72= Te6edf3dataTff7b72.Td2a8ffencodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)
Tff7b72with Tffa657openTb4b4b4(Te6edf3file_pathTb4b4b4, Ta5d6ff"Ta5d6ffwbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3fTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3dataTb4b4b4)
Tff7b72if Tff7b72selfTff7b72.Td2a8ffrunningTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_register_file_handlerTb4b4b4(Te6edf3nameTb4b4b4)
Tff7b72return Te6edf3name

Tff7b72def Td2a8ffremove_fileTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3nameTb4b4b4)Tb4b4b4:
T8b949e"""Remove a file and deregister its request handler."""
Tff7b72tryTb4b4b4:
Te6edf3name Tff7b72= Te6edf3_safe_mesh_file_basenameTb4b4b4(Te6edf3nameTb4b4b4)
Te6edf3_reject_name_component_too_longTb4b4b4(Tff7b72selfTff7b72.Td2a8fffiles_dirTb4b4b4, Te6edf3nameTb4b4b4)
Tff7b72except Tf85149ValueErrorTb4b4b4:
Tff7b72return Tff7b72False
Te6edf3file_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Tff7b72selfTff7b72.Td2a8fffiles_dirTb4b4b4, Te6edf3nameTb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3file_pathTb4b4b4)Tb4b4b4:
Te6edf3osTff7b72.Td2a8ffremoveTb4b4b4(Te6edf3file_pathTb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_deregister_file_handlerTb4b4b4(Te6edf3nameTb4b4b4)
Tff7b72return Tff7b72True
Tff7b72return Tff7b72False

Tff7b72def Td2a8fflist_filesTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Return a list of file dicts with name and size."""
Tff7b72if Tff7b72selfTff7b72.Td2a8ffrunningTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_register_existing_filesTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisdirTb4b4b4(Tff7b72selfTff7b72.Td2a8fffiles_dirTb4b4b4)Tb4b4b4:
Tff7b72return Tb4b4b4[Tb4b4b4]
Te6edf3result Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3fname Tff7b72in Tffa657sortedTb4b4b4(Te6edf3osTff7b72.Td2a8fflistdirTb4b4b4(Tff7b72selfTff7b72.Td2a8fffiles_dirTb4b4b4)Tb4b4b4)Tb4b4b4:
Te6edf3fpath Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Tff7b72selfTff7b72.Td2a8fffiles_dirTb4b4b4, Te6edf3fnameTb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3fpathTb4b4b4)Tb4b4b4:
Te6edf3resultTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4{Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4: Te6edf3fnameTb4b4b4, Ta5d6ff"Ta5d6ffsizeTa5d6ff"Tb4b4b4: Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffgetsizeTb4b4b4(Te6edf3fpathTb4b4b4)Tb4b4b4}Tb4b4b4)
Tff7b72return Te6edf3result

Tff7b72def Td2a8ffget_destination_hashTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Return the hex destination hash if running."""
Tff7b72if Tff7b72selfTff7b72.Td2a8ffdestinationTb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffdestinationTff7b72.Td2a8ffhashTff7b72.Td2a8ffhexTb4b4b4(Tb4b4b4)
Tff7b72return Tff7b72None

Tff7b72def Td2a8ffget_statusTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Return current node status dict."""
Te6edf3uptime_seconds Tff7b72= T79c0ff0
Tff7b72if Tff7b72selfTff7b72.Td2a8ffrunning Tff7b72and Tff7b72selfTff7b72.Td2a8ff_serve_started_at Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Te6edf3uptime_seconds Tff7b72= Tffa657maxTb4b4b4(T79c0ff0Tb4b4b4, Tffa657intTb4b4b4(Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4) Tff7b72- Tff7b72selfTff7b72.Td2a8ff_serve_started_atTb4b4b4)Tb4b4b4)
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffnode_idTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffnode_idTb4b4b4,
Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffnameTb4b4b4,
Ta5d6ff"Ta5d6ffrunningTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffrunningTb4b4b4,
Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffget_destination_hashTb4b4b4(Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffidentity_hashTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffidentityTff7b72.Td2a8ffhashTff7b72.Td2a8ffhexTb4b4b4(Tb4b4b4) Tff7b72if Tff7b72selfTff7b72.Td2a8ffidentity Tff7b72else Tff7b72NoneTb4b4b4,
Ta5d6ff"Ta5d6ffactive_linksTa5d6ff"Tb4b4b4: Tffa657lenTb4b4b4(Tff7b72selfTff7b72.Td2a8ffactive_linksTb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffunique_connectionsTa5d6ff"Tb4b4b4: Tffa657lenTb4b4b4(Tff7b72selfTff7b72.Td2a8ff_unique_remote_hashesTb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffuptime_secondsTa5d6ff"Tb4b4b4: Te6edf3uptime_secondsTb4b4b4,
Ta5d6ff"Ta5d6ffpagesTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8fflist_pagesTb4b4b4(Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6fffilesTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8fflist_filesTb4b4b4(Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffstatsTa5d6ff"Tb4b4b4: Tffa657dictTb4b4b4(Tff7b72selfTff7b72.Td2a8ff_statsTb4b4b4)Tb4b4b4,
Tb4b4b4}

Tff7b72def Td2a8ffsave_configTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Persist node configuration to disk."""
Te6edf3config Tff7b72= Tb4b4b4{
Ta5d6ff"Ta5d6ffnode_idTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffnode_idTb4b4b4,
Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffnameTb4b4b4,
Tb4b4b4}
Te6edf3config_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Tff7b72selfTff7b72.Td2a8ffbase_dirTb4b4b4, Ta5d6ff"Ta5d6ffconfig.jsonTa5d6ff"Tb4b4b4)
Tff7b72with Tffa657openTb4b4b4(Te6edf3config_pathTb4b4b4, Ta5d6ff"Ta5d6ffwTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3jsonTff7b72.Td2a8ffdumpTb4b4b4(Te6edf3configTb4b4b4, Te6edf3fTb4b4b4, Te6edf3indentTff7b72=T79c0ff2Tb4b4b4)

Tf0883e@staticmethod
Tff7b72def Td2a8ffload_configTb4b4b4(Te6edf3base_dirTb4b4b4)Tb4b4b4:
T8b949e"""Load node configuration from disk. Returns dict or None."""
Te6edf3config_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3base_dirTb4b4b4, Ta5d6ff"Ta5d6ffconfig.jsonTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3config_pathTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Tff7b72with Tffa657openTb4b4b4(Te6edf3config_pathTb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Tff7b72return Te6edf3jsonTff7b72.Td2a8ffloadTb4b4b4(Te6edf3fTb4b4b4)


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────